package com.xiao.sulingwallet.controller;

import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.internal.util.StringUtils;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiao.sulingwallet.config.AlipayH5Config;
import com.xiao.sulingwallet.constant.RedisConster;
import com.xiao.sulingwallet.entity.PurseEntity;
import com.xiao.sulingwallet.entity.TradeStateEntity;
import com.xiao.sulingwallet.feign.OrderFeign;
import com.xiao.sulingwallet.service.AlipayService;
import com.xiao.sulingwallet.service.PurseService;
import com.xiao.sulingwallet.service.TradeStateService;
import com.xiao.sulingwallet.vo.HelpFormDataVo;
import com.xiao.sulingwallet.vo.ReleaseOrderVo;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author: 1322127758@qq.com
 * @date: 2022/3/14 17:41
 * @作用: 支付宝支付
 */
@RestController
@RequestMapping("/sulingwallet/alipay")
public class AlipayController {
    @Autowired
    private AlipayService alipayService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * rabbitMQ发送消息 和 获取消息的对像
     */
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private PurseService purseService;

    @Autowired
    private TradeStateService tradeStateService;

    @Autowired
    private OrderFeign orderFeign;

    @Autowired
    private ThreadPoolExecutor executor;

    /**
     * 发起支付请求  电脑网页支付
     * @param subject :支付时显示的名
     * @param money   :支付的金额
     * @param orderSn :订单号
     * @return :返回支付表单
     */
    @RequestMapping("/pay")
    public String aliPay(String subject, BigDecimal money, String orderSn){
        try {
            return alipayService.getPayForm(subject,money,orderSn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 前端吧订单号发过来,服务器获取订单信息,返回支付表单, 只是多做了一层controller 调用aliPayH5()
     * @param subject 支付时显示的名
     * @param orderSn 订单号
     * @param username 用户账号
     */
    @RequestMapping("/frontend/pay")
    public void actingAliPayH5(String subject, String orderSn, String username, HttpServletResponse response){
        /**
         * 1.使用订单号 查询获取订单对应的商品信息
         */
        //构建key
        String key = RedisConster.NOPAY_ORDER_KEY + username + ":" + orderSn;
        String s = redisTemplate.opsForValue().get(key);
        if(!StringUtils.isEmpty(s)){
            //获取数据是帮领订单的数据则releaseOrderVo不为null, 主要数据类型不一样
            ReleaseOrderVo releaseOrderVo = JSON.parseObject(s, ReleaseOrderVo.class);
            BigDecimal totalPrice = releaseOrderVo != null ? releaseOrderVo.getOrOrderTotalPrice() : null;
            // 判断如果为null那么就是帮买订单的数据
            if (totalPrice == null || totalPrice.compareTo(BigDecimal.ZERO)==0){
                HelpFormDataVo helpFormDataVo = JSON.parseObject(s, HelpFormDataVo.class);
                totalPrice = helpFormDataVo.getTotalPrice();
            }

            if(null != totalPrice){
                /**
                 * 发起支付请求 H5支付
                 * @param subject :支付时显示的名
                 * @param money   :支付的金额
                 * @param orderSn :订单号
                 * @return :返回支付表单
                 */
                //构建数据
                if(StringUtils.isEmpty(subject)){
                    //如果没指定使用 我们自己指定的
                    subject = "速领 吧!";
                }
                try {
                    String form = aliPayH5(subject, totalPrice, orderSn);
                    response.setContentType("text/html;charset=" + AlipayH5Config.CHARSET);
                    response.getWriter().write(form);
                    response.getWriter().flush();
                    response.getWriter().close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 发起支付请求 H5支付, 不对外开放
     * @param subject :支付时显示的名
     * @param money   :支付的金额
     * @param orderSn :订单号
     * @return :返回支付表单
     */
    @RequestMapping("/payh5")
    public String aliPayH5(String subject, BigDecimal money, String orderSn) throws AlipayApiException {
        // 商户订单号，商户网站订单系统中唯一订单号，必填
        String out_trade_no = orderSn;
        // 订单名称，必填
        subject = subject;
        // 付款金额，必填
        String total_amount = String.valueOf(money);
        // 商品描述，可空
        String body = "支付";
        // 超时时间 可空
        String timeout_express ="5m";
        // 销售产品码 必填 默认
        String product_code = "QUICK_WAP_WAY";
        /**********************/
        // SDK 公共请求类，包含公共请求参数，以及封装了签名与验签，开发者无需关注签名与验签
        //调用RSA签名方式
        AlipayClient client = new DefaultAlipayClient(AlipayH5Config.URL, AlipayH5Config.APPID, AlipayH5Config.RSA_PRIVATE_KEY, AlipayH5Config.FORMAT, AlipayH5Config.CHARSET, AlipayH5Config.ALIPAY_PUBLIC_KEY,AlipayH5Config.SIGNTYPE);
        AlipayTradeWapPayRequest alipay_request=new AlipayTradeWapPayRequest();

        // 封装请求支付信息
        AlipayTradeWapPayModel model=new AlipayTradeWapPayModel();
        model.setOutTradeNo(out_trade_no);
        model.setSubject(subject);
        model.setTotalAmount(total_amount);
        model.setBody(body);
        model.setTimeoutExpress(timeout_express);
        model.setProductCode(product_code);
        alipay_request.setBizModel(model);
        // 设置异步通知地址
        alipay_request.setNotifyUrl(AlipayH5Config.notify_url);
        // 设置同步地址
        alipay_request.setReturnUrl(AlipayH5Config.return_url);

        // form表单生产
        String form = "";
        // 调用SDK生成表单
        form = client.pageExecute(alipay_request).getBody();
//        response.setContentType("text/html;charset=" + AlipayH5Config.CHARSET);
//        response.getWriter().write(form);//直接将完整的表单html输出到页面
//        response.getWriter().flush();
//        response.getWriter().close();
        return form;
    }

    /**
     * 支付成功后的同步回调
     */
    @RequestMapping("/payh5/asysuccess.html")
    public void payAsySuccess02(HttpServletResponse response) throws IOException {

        Resource resource = new ClassPathResource("static/test.txt");
        //获1.txt的取相对路径
        String path = resource.getFile().getPath();
        System.out.println(path);
        //读取1.txt中的内容
        InputStream is = resource.getInputStream();
        InputStreamReader isr = new InputStreamReader(is);
        String s = new BufferedReader(isr)
                .lines().collect(Collectors.joining(System.lineSeparator()));

        response.setContentType("text/html;charset=" + AlipayH5Config.CHARSET);
        response.getWriter().write(s);//直接将完整的表单html输出到页面
        response.getWriter().flush();
        response.getWriter().close();
    }

    /**
     * 支付成功后的回调  (验签)
     */
    @Transactional(rollbackFor = Exception.class)
    @PostMapping("/payh5/asysuccess")
    public void payAsySuccess(HttpServletRequest request,HttpServletResponse response) throws Exception {

        //将异步通知中收到的所有参数都存放到map中
        /*
         * trade_no	支付宝 28 位交易号。
         * trade_status	交易状态。
         * total_amount	订单金额。
         */
        Map<String, String[]> requestMap = request.getParameterMap();
        Map<String, String> paramsMap = new HashMap<>();
        requestMap.forEach((key, values) -> {
            String strs = "";
            for(String value : values) {
                strs = strs + value;
            }
            System.out.println(("key值为"+key+"value为："+strs));
            paramsMap.put(key, strs);
        });
        //订单号
        String orderSn = paramsMap.get("out_trade_no");
        //获取价格
        String price = paramsMap.get("total_amount");
        //转账时间
        String date = paramsMap.get("gmt_payment");

        //调用SDK验证签名
        boolean b = AlipaySignature.rsaCheckV1(paramsMap, AlipayH5Config.ALIPAY_PUBLIC_KEY, AlipayH5Config.CHARSET, AlipayH5Config.SIGNTYPE);//调用SDK验证签名
        if(b){
            // TODO 验签成功后，按照支付结果异步通知中的描述，对支付结果中的业务内容进行二次校验，校验成功后在response中返回success并继续商户自身业务处理，校验失败返回failure
            //支付成功
            //TODO 操作...
            System.out.println("支付成功");

            CompletableFuture.runAsync(()->{
                //1.hint 保存支付成功后的订单号进入队列  往哪个交换机发送消息suling.exchange, 发到哪个key的队列paysuccess.queue.key
                rabbitTemplate.convertAndSend("suling.exchange","paysuccess.queue.key", orderSn);

                //异步修改订单表的支付状态
                orderFeign.updatePayState(orderSn,"1");
            },executor);


            //2.先获取用户号
            String username = redisTemplate.opsForValue().get(RedisConster.USERNAME_ORDER_KEY + orderSn);
            if(!StringUtils.isEmpty(username)){

                String key = username + ":" + orderSn;
                //将没支付时的订单数据获取出来
                String s = redisTemplate.opsForValue().get(RedisConster.NOPAY_ORDER_KEY + key);
                if(!StringUtils.isEmpty(s)){

                    //将没支付时的订单数据放到 支付后的redis中
                    redisTemplate.opsForValue().set(RedisConster.ORDER_KEY + key, s, 24, TimeUnit.HOURS);

                    //删除
                    redisTemplate.delete(RedisConster.USERNAME_ORDER_KEY + orderSn);
                    //删除没支付时的存放在redis的订单数据
                    redisTemplate.delete(RedisConster.NOPAY_ORDER_KEY + key);

                    //3.hint 交易信息保存到数据库中
                    PurseEntity purseEntity1 = new PurseEntity();
                    TradeStateEntity tradeStateEntity = new TradeStateEntity();

                    PurseEntity purseEntity = purseService.getOne(new QueryWrapper<PurseEntity>().eq("pu_member_id", "1322127758"));
                    if (null != purseEntity){
                        BigDecimal puAmount = purseEntity.getPuAmount();
                        BigDecimal add = puAmount.add(new BigDecimal(price));
                        purseEntity1.setPuAmount(add);

                        boolean pu_member_id = purseService.update(purseEntity1, new QueryWrapper<PurseEntity>().eq("pu_member_id", "1322127758"));
                        if(!pu_member_id){
                            throw new Exception("保存支付数据错误");
                        }
                    }else{
                        purseEntity1.setPuAmount(new BigDecimal(price));
                        purseEntity1.setPuMemberId("1322127758");

                        boolean save = purseService.save(purseEntity1);
                        if(!save){
                            throw new Exception("保存支付数据错误");
                        }
                    }

                    //保存交易详情
                    tradeStateEntity.setTrsAmount(new BigDecimal(price));
                    //对公用户
                    tradeStateEntity.setTrsMemberId("1322127758");
                    //转账人
                    tradeStateEntity.setTrsTransferUser(username);
                    tradeStateEntity.setTrsTradeType(1);

                    tradeStateEntity.setTrsCreateDate(new Date());

                    boolean save = tradeStateService.save(tradeStateEntity);
                    if(!save){
                        throw new Exception("保存支付数据错误");
                    }

                }
            }

            /*
             * 返回数据让支付宝知道我们收到信息了
             */
            response.getWriter().write("success");
            //关闭流
            response.getWriter().flush();
            response.getWriter().close();
            return;
        }

        // TODO Auto-generated catch block
        System.out.println("*********************验签失败********************");
        // TODO 验签失败则记录异常日志，并在response中返回failure.
        /*
         * 返回数据让支付宝知道我们收到信息了
         */
        response.getWriter().write("failure");
        //关闭流
        response.getWriter().flush();
        response.getWriter().close();
    }

    /**
     * 电脑端的支付成功回调 (验签) 异步处理支付成功请求 ,暂时使用同步通知
     * @GlobalTransactional :开启分布式事务
     * @param out_trade_no :我们自己的订单id  (不是支付流水号)
     * @param trade_status :支付的状态
     */
    @PostMapping("/pay/asysuccess")
    @ResponseBody
    public void payAsySuccess(String out_trade_no, String trade_status, HttpServletResponse response) throws IOException {
        //TODO 保存支付流水号

        /*
         * WAIT_BUYER_PAY ：交易创建，等待买家付款。
         * TRADE_CLOSED ：在指定时间段内未支付时关闭的交易或在交易完成全额退款成功时关闭的交易。
         * TRADE_SUCCESS：商户签约的产品支持退款功能的前提下，买家付款成功。
         * TRADE_FINISHED：商户签约的产品不支持退款功能的前提下，买家付款成功；或者，商户签约的产品支持退款功
         */
        if ("TRADE_SUCCESS".equals(trade_status)) {
            //1.修改订单状态为 已支付状态
            System.out.println("支付成功");
        }
        /*
         *异步回调数据接收成功，在执行完业务逻辑后，没有返回success这7个字符给支付宝，导致支付宝服务器不断重复发送通知。
         *重复通知特性如下：25小时以内完成8次通知（通知的间隔频率一般是4m,10m,10m,1h,2h,6h,15h）
         *需要发送success回支付宝
         */
        response.getWriter().write("success");
        response.getWriter().flush();
        response.getWriter().close();
    }
}
